package com.google.code.pomhelper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.xmlbeans.XmlException;

import com.google.code.pomhelper.schema.PluginXmlBean;
import com.google.code.pomhelper.schema.ProjectDocumentXmlBean;

/**
 * Analyzes the <code>&lt;plugins/&gt;</code> and <code>&lt;pluginManagement/&gt;</code> tags in the <code>pom.xml</code> and
 * determines the duplicate declared plugins.
 * 
 * @author rm
 * 
 */
@Mojo(name = "analyze-duplicatePlugins")
public class AnalyzePluginsMojo extends AbstractMojo {
	/**
	 * The Maven project to analyze.
	 */
	@Component
	private MavenProject project;

	/**
	 * Skip plugin execution completely.
	 */
	@Parameter(property = "analyze-duplicatePlugins.skip", defaultValue = "false")
	private boolean skip;

	/**
	 * Fail the build if a problem is detected.
	 */
	@Parameter(property = "analyze-duplicatePlugins.failBuild", defaultValue = "false")
	private boolean failBuild;

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {
		if (isSkip()) {
			getLog().info("Skipping plugin execution");
			return;
		}

		if (getLog().isInfoEnabled()) {
			try {
				/*
				 * project.getPluginArtifacts() and project.getPluginManagement().getPlugins() could not be used here.
				 */

				ProjectDocumentXmlBean projectDocument = ProjectDocumentXmlBean.Factory.parse(project.getFile());
				StringBuilder sb = new StringBuilder();

				if (projectDocument.getProject().isSetBuild() && projectDocument.getProject().getBuild().isSetPlugins()) {
					Collection<String> duplicatePlugins = findDuplicatePlugins(projectDocument.getProject().getBuild()
							.getPlugins().getPluginArray());

					appendDuplicates(sb, duplicatePlugins, "<plugins/>");
				}

				if (projectDocument.getProject().isSetBuild() && projectDocument.getProject().getBuild().isSetPluginManagement()
						&& projectDocument.getProject().getBuild().getPluginManagement().isSetPlugins()) {
					Collection<String> duplicatePlugins = findDuplicatePlugins(projectDocument.getProject().getBuild()
							.getPluginManagement().getPlugins().getPluginArray());

					appendDuplicates(sb, duplicatePlugins, "<pluginManagement/>");

				}

				if (sb.length() > 0) {
					getLog().info(sb.toString());

					if (failBuild) {
						throw new MojoExecutionException("Found duplicate plugins.");
					}
				} else {
					getLog().info("No duplicate dependencies found in <dependencies/> or in <dependencyManagement/>");
				}

			} catch (XmlException e) {
				throw new MojoExecutionException(e.getMessage(), e);
			} catch (IOException e) {
				throw new MojoExecutionException(e.getMessage(), e);
			}
		}
	}

	/**
	 * build the output-{@link String}.
	 * 
	 * @param sb
	 * @param duplicatePlugins
	 * @param xmlTag
	 */
	private void appendDuplicates(StringBuilder sb, Collection<String> duplicatePlugins, String xmlTag) {
		if (!duplicatePlugins.isEmpty()) {
			sb.append("List of duplicate plugins defined in " + xmlTag + " in your pom.xml:\n");

			for (String plugin : duplicatePlugins) {
				sb.append("\to ");
				sb.append(plugin);
				sb.append("\n");
			}
		}
	}

	@SuppressWarnings("unchecked")
	private Collection<String> findDuplicatePlugins(PluginXmlBean[] plugins) {
		List<String> pluginsList = new ArrayList<String>();

		if (plugins != null && plugins.length > 1) {

			for (PluginXmlBean plugin : plugins) {
				pluginsList.add(plugin.getGroupId() + ":" + plugin.getArtifactId());
			}

		}

		return CollectionUtils.disjunction(pluginsList, new HashSet<String>(pluginsList));
	}

	/**
	 * @return the skip
	 */
	public boolean isSkip() {
		return skip;
	}

	/**
	 * @param skip
	 *            the skip to set
	 */
	public void setSkip(boolean skip) {
		this.skip = skip;
	}

	/**
	 * @return the failBuild
	 */
	public boolean isFailBuild() {
		return failBuild;
	}

	/**
	 * @param failBuild
	 *            the failBuild to set
	 */
	public void setFailBuild(boolean failBuild) {
		this.failBuild = failBuild;
	}
}
